/*
  Time-stamp: <2009-09-29 19:40:31 xinhaoyuan>

  File: elf.c

  This file provides function to manipulate with ELF binary format.
  The EKOS userspace lib can create new process with few helps from
  kernel.

  For a clear view of userspace memory layout, see document for more
  info.

  This module is invoked by user process manager, which is nearly a
  skeleton process contains only user base lib. When the manager
  receive a request that need to create a new process. The manager
  forks, then loads the elf file to the forked process. And start
  executing.

  Codes in this module are executed after the forking and before the
  user program runs.
  
*/

#include <user/ulib.h>

#include <inc/elf.h>

int
elf_load(struct file_t *file, uintptr_t *entry)
{
	 struct elf_t elf;

	 file_seek(file, FS_SEEK_BEGIN, 0);
	 file_read(file, &elf, sizeof(struct elf_t));
	 file_seek(file, FS_SEEK_BEGIN, elf.e_phoff);

	 struct proghdr_t
		  *phs = malloc(elf.e_phnum * sizeof(struct proghdr_t)),
		  *eph = phs + elf.e_phnum;

	 file_read(file, phs, elf.e_phnum * sizeof(struct proghdr_t));

	 struct proghdr_t *ph = phs;
	 for ( ; ph != eph; ++ph)
     {
		  if (ph->p_type != ELF_PROG_LOAD) continue;
		  char *buf = (char *)malloc(ph->p_filesz);
		  
		  file_seek(file, FS_SEEK_BEGIN, ph->p_offset);
		  file_read(file, buf, ph->p_filesz);
		  
		  if (mmove((char *)ph->p_va, buf,
					0, ph->p_memsz, ph->p_filesz))
			   kprintf("move error.\n");
     }

	 *entry = elf.e_entry;
}

int
mmove(char *dest, char *base, size_t offset, size_t fillsize, size_t copysize)
{
     kprintf(" -- trying to move [%x, %x] to [%x, %x]\n",
			 base + offset, base + offset + copysize,
			 dest, dest + fillsize);
     base = base + offset; offset = 0;

     uintptr_t r = (uintptr_t)dest & (PAGE_SIZE - 1);
     
     int err;
     uint32_t perm;
     
     if (r)
     {
	 	  r = PAGE_SIZE - r;
	 	  perm = PTE_W | PTE_U;
	 	  if ((err = alloc_page(dest, perm)) != 0)
	 	  {
	 		   return err;
	 	  }
		       
	 	  if (r > copysize)
	 	  {
	 		   memmove(dest, base, copysize);
	 		   memset(dest, 0, r - copysize);
	       
	 	  }
	 	  else
	 	  {
	 		   memmove(dest, base, r);
	 	  }
	  
	 	  dest += r;
	 	  base += r;
	 	  offset += r;
     }
     
     while (offset < fillsize)
     {
	 	  perm = PTE_W | PTE_U;
	 	  if ((err = alloc_page(dest, perm)) != 0)
	 	  {
	 		   return err;
	 	  }
	  
	 	  if (offset + PAGE_SIZE <=  copysize)
	 		   memmove(dest, base, PAGE_SIZE);
	 	  else if (offset < copysize)
	 	  {
	 		   memmove(dest, base, copysize - offset);
	 		   memset(dest + copysize - offset, 0, PAGE_SIZE - copysize + offset);
	 	  }
	 	  else memset(dest, 0, PAGE_SIZE);

	 	  dest += PAGE_SIZE;
	 	  base += PAGE_SIZE;
	 	  offset += PAGE_SIZE;
     }

     return 0;
}

